﻿#pragma once

#include "Misc/EngineEditorMacros.h"
#include "Object/Object.h"
#include "Path/Path.h"
#include "EditorPageModel.m.h"

class REditorPageModel;
class REditorPageViewModel;

RCLASS()
class REditorPageOperation : public RObject
{
    ROBJECT

public:
    TVector<RObjectPtr> Objects;

    TDelegate<void, TSharedObjectPtr<REditorPageModel>> RedoFunction;
    TDelegate<void, TSharedObjectPtr<REditorPageModel>> UndoFunction;
    TDelegate<bool, TSharedObjectPtr<REditorPageOperation>> MergeFunction;
    void Redo(TSharedObjectPtr<REditorPageModel> PageModel)
    {
        RedoFunction.ExecuteIfBound(PageModel);
    }

    void Undo(TSharedObjectPtr<REditorPageModel> PageModel)
    {
        UndoFunction.ExecuteIfBound(PageModel);
    }

    //try to merge with former operation
    bool Merge(TSharedObjectPtr<REditorPageOperation> Operation) const
    {
        if(MergeFunction.IsBound())
        {
            return MergeFunction.Execute(Operation);
        }
        return false;
    }

};


RCLASS()
class REditorPageModel : public RObject
{
    ROBJECT

public:
    //当前绑定的视图模型
    TWeakObjectPtr<REditorPageViewModel> ViewModel;


    void Save()
    {
        if(HasUnsavedData())
        {
            LowLevelSave();
            LastOperationBeforeSave=GetLastOperation();
        }
    }   

    //复制一个新的页面，共享正在编辑的数据
    virtual TSharedObjectPtr<REditorPageModel> ShareClone(RObject* InOwner) const
    {
        //子类应该重写这个函数
        assert(false);
        return nullptr;
    };

    //get  file id
    //generally, it is the file path
    virtual CString GetFileID() const { 
        return GetFilePath().ToString(); 
    }

    //get file path
    //if don't have a file path, return an empty path
    virtual SPath GetFilePath() const
    {
        return SPath();
    }

    //get the object that is focused
    //generally, it is the object that is selected
    //if don't have a focused object, return nullptr
    virtual TSharedObjectPtr<RObject> GetFocuseObject() const
    {
        return nullptr;
    }

    //let global model know that the focused object has changed
    //if the focused object is changed, you should call this function
    void NotifyFocusedObjectChanged();

protected:
    //save the file 
    virtual void LowLevelSave()
    {
        //subclass should override this function
        assert(false);
    }


public:
//undo redo:
    void AddAndRedoOperation(TSharedObjectPtr<REditorPageOperation> Operation)
    {
        // Clear all operations after current operation
        if(CurrentOperationIndex < int64_t(Operations.Num())-1)
        {
            Operations.Resize(CurrentOperationIndex+1);
        }

        // Merge with last operation
        if(Operations.Empty() || !Operation->Merge(Operations.Last()))
        {
            Operations.Add(Operation);
            CurrentOperationIndex++;
        }

        Operations[CurrentOperationIndex]->Redo(this);
    }

    virtual bool HasUnsavedData() const;

    virtual bool CanRedo() const
    {
        return CurrentOperationIndex < int64_t(Operations.Num())-1;
    }
    virtual bool Redo()
    {
        if (CurrentOperationIndex <int64_t(Operations.Num())-1)
        {
            CurrentOperationIndex++;
            Operations[CurrentOperationIndex]->Redo(this);

            return true;
        }

        return false;
    }

    virtual bool CanUndo() const
    {
        return CurrentOperationIndex >= 0;
    }
    virtual bool Undo()
    {
        if (CurrentOperationIndex >= 0)
        {
            Operations[CurrentOperationIndex]->Undo(this);
            CurrentOperationIndex--;

            return true;
        }

        return false;
    }

    virtual bool SupportFindAndReplaceText()
    {
        return false;
    }

protected:
    TSharedObjectPtr<REditorPageOperation> GetLastOperation() const
    {
        if (CurrentOperationIndex >= 0)
        {
            return Operations[CurrentOperationIndex];
        }
        return nullptr;
    }

    TVector<TSharedObjectPtr<REditorPageOperation>> Operations;
    TSharedObjectPtr<REditorPageOperation> LastOperationBeforeSave;
    int64_t CurrentOperationIndex = -1;

};